import java.util.*;
/**
 * 合并K个有序链表
 * 将K个升序链表合并为一个新的升序链表并返回
 */
public class mergeKLists {
    /**
     * 链表节点类
     * 包含节点值和指向下一个节点的引用
     */
    public static class ListNode{
        int val;        // 节点值
        ListNode next;  // 指向下一个节点的引用
        
        // 无参构造函数
        ListNode(){}
        
        // 带值的构造函数
        ListNode(int val){
            this.val = val;
        }
        
        // 带值和下一个节点的构造函数
        ListNode(int val, ListNode next){
            this.val = val;
            this.next = next;
        }
    }

    /**
     * 合并K个有序链表的主方法
     * @param lists 包含K个有序链表的数组
     * @return 合并后的新链表
     */
    public static ListNode mergeKListsM(ListNode[] lists){
        // 处理空输入的情况
        if(lists == null || lists.length == 0){
            return null;
        }
        // 使用分治法合并链表
        return mergeList(lists, 0, lists.length - 1);
    }

    /**
     * 分治法合并链表数组中的链表
     * @param lists 链表数组
     * @param left 左边界
     * @param right 右边界
     * @return 合并后的链表
     */
    public static ListNode mergeList(ListNode[] lists, int left, int right){
        // 当只有一个链表时，直接返回
        if(left == right){
            return lists[left];
        }
        // 计算中间位置
        int mid = (left + right) >> 1;
        // 递归合并左半部分
        ListNode l = mergeList(lists, left, mid);
        // 递归合并右半部分
        ListNode r = mergeList(lists, mid + 1, right);
        // 合并两个有序链表
        return merge2List(l, r);
    }

    /**
     * 合并两个有序链表
     * @param l 第一个有序链表
     * @param r 第二个有序链表
     * @return 合并后的新链表
     */
    public static ListNode merge2List(ListNode l, ListNode r){
        // 创建虚拟头节点
        ListNode dummy = new ListNode(0);
        ListNode cur = dummy;
        
        // 当两个链表都不为空时，比较节点值并合并
        while(l != null && r != null){
            if(l.val > r.val){
                // 如果r的节点值较小，创建新节点并连接到结果链表
                cur.next = new ListNode(r.val);
                cur = cur.next;
                r = r.next;
            }else{
                // 如果l的节点值较小或相等，创建新节点并连接到结果链表
                cur.next = new ListNode(l.val);
                cur = cur.next;
                l = l.next;
            }
        }
        // 处理剩余节点，将非空的链表直接连接到结果链表
        cur.next = l == null ? r : l;
        return dummy.next;
    }

    /**
     * 主函数：处理输入输出
     * 从控制台读取K个有序链表，合并后输出结果
     */
    public static void main(String[] args){
        Scanner sc = new Scanner(System.in);
        // 读取链表数量K
        int k = sc.nextInt();
        ListNode[] lists = new ListNode[k];
        // 消耗掉nextInt()后的换行符
        sc.nextLine();
        
        // 读取K个链表
        for(int i = 0; i < lists.length; i++){
            String[] s = sc.nextLine().split(" ");
            // 创建链表头节点
            lists[i] = new ListNode(Integer.parseInt(s[0]));
            ListNode ptr = lists[i];
            // 构建链表
            for(int j = 1; j < s.length; j++){
                ptr.next = new ListNode(Integer.parseInt(s[j]));
                ptr = ptr.next;
            }
        }
        
        // 合并链表并输出结果
        ListNode result = mergeKListsM(lists);
        while(result != null){
            System.out.print(result.val + " ");
            result = result.next;
        }
        sc.close();
    }
}
